package com.system.core.security.jwt;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.TokenExpiredException;
import com.auth0.jwt.impl.NullClaim;
import com.auth0.jwt.interfaces.Claim;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.system.core.interceptor.jwt.JwtConfigProperties;
import com.system.core.interceptor.jwt.JwtTokenStoreStrategy;
import com.system.core.message.GenericException;
import com.system.core.message.LoginExpiredException;
import com.system.core.security.jwt.dto.UserJWT;

import lombok.extern.slf4j.Slf4j;

@Component
@Slf4j
public class JwtService {

    @Autowired
    private JwtConfigProperties jwtConfigProperties;

    @Autowired
    private JwtTokenStoreStrategy<UserJWT> jwtTokenStoreStrategy;
    
    private SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    /**
     * 通过jwt的user对象生成token字符串
     *
     * @param user
     * @return
     */
    public String createToken(UserJWT user) {
        return this.createToken(user, 0);
    }

    /**
     * 通过jwt的user对象生成token字符串
     *
     * @param user      jwt user信息对象
     * @param tokenType
     * @return token
     */
    public String createToken(UserJWT user,int tokenType) {
    	SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime expire = now.plusDays(jwtConfigProperties.getExpireday());
        Date expiresAt = Date.from(expire.atZone(ZoneId.systemDefault()).toInstant());

        //生成Token
        String token;
        JWTCreator.Builder builder = JWT.create().withSubject(user.getSubject());
        JSONObject jsonObject = JSONObject.parseObject(JSONObject.toJSONString(user));
        for (Map.Entry<String, Object> entry : jsonObject.entrySet()) {
            if (!"subject".equals(entry.getKey())) {
                builder.withClaim(entry.getKey(), String.valueOf(entry.getValue()));
            }
        }
        token = builder.withExpiresAt(expiresAt).sign(Algorithm.HMAC256(jwtConfigProperties.getSecret()));
        log.info("[jwt]token:{},申请时间{}，有效期至{}",token,format.format(new Date()),format.format(expiresAt));
        //存储
        jwtTokenStoreStrategy.store(user, token, Duration.ofDays(jwtConfigProperties.getExpireday()), tokenType);

        //返回结果
        return token;
    }

    /**
     * 校验token，如果token合法，则通过jwt解析出token中的user信息返回user对象UserJWT
     *
     * @param token
     * @param clazz
     * @return jwt user对象
     * @throws Exception
     */
    public <T extends UserJWT> T verifyToken(String token, Class<T> clazz) throws Exception {
        //获取jwt
        DecodedJWT jwt;
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(jwtConfigProperties.getSecret())).build();
            jwt = verifier.verify(token);
        } catch (TokenExpiredException e) {
        	String expireDate = dateFormat.format(JWT.decode(token).getExpiresAt());
            throw new LoginExpiredException("登陆超过" + jwtConfigProperties.getExpireday() + "天已过期(过期时间："+expireDate+")，为了您的账户安全，请重新登录。");
        } catch (Exception e) {
            throw new GenericException(GenericException.APP_LOGOUT_CODE, "token无效:"+e.getMessage());
        }

        //将参数映射至实体类中
        Map<String, Claim> map = jwt.getClaims();
        T jwtObject = BeanUtils.instantiateClass(clazz);
        jwtObject.setSubject(jwt.getSubject());
        List<Field[]> fieldList = this.getAllDeclaredFields(clazz);
        for (Field[] fields : fieldList) {
            for (Field field : fields) {
                if (!Modifier.isFinal(field.getModifiers())) {                  //final的字段不处理
                    if (!"subject".equals(field.getName())) {                   //subject不处理
                        field.setAccessible(true);
                        if (field.getType().equals(String.class)) {
                            ReflectionUtils.setField(field, jwtObject, map.getOrDefault(field.getName(), new NullClaim()).asString());
                        } else if (field.getType().equals(Long.class)) {
                            ReflectionUtils.setField(field, jwtObject, map.getOrDefault(field.getName(), new NullClaim()).asLong());
                        } else if (field.getType().equals(Integer.class)) {
                            ReflectionUtils.setField(field, jwtObject, map.getOrDefault(field.getName(), new NullClaim()).asInt());
                        } else {
                            //其它的暂时不处理
                        }
                    }
                }
            }
        }
        log.debug("[JWT]用户验证通过，用户信息是：{}", jwtObject);
        return jwtObject;
    }

    /**
     * 获取全部的字段
     *
     * @param clazz
     * @return
     */
    private List<Field[]> getAllDeclaredFields(Class<?> clazz) {
        List<Field[]> fieldArrayList = new ArrayList<>();
        while (clazz != null) {
            fieldArrayList.add(clazz.getDeclaredFields());
            clazz = clazz.getSuperclass();
        }
        return fieldArrayList;
    }
}
